package uk.ac.glasgow.demosocs.impl;

import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.Set;

import com.sun.corba.se.impl.encoding.OSFCodeSetRegistry.Entry;

import uk.ac.glasgow.clui.SystemCommandException;
import uk.ac.glasgow.demosocs.Candidate;
import uk.ac.glasgow.demosocs.Election;
import uk.ac.glasgow.demosocs.ElectionResult;
import uk.ac.glasgow.demosocs.ElectionState;
import uk.ac.glasgow.demosocs.Vote;
import uk.ac.glasgow.demosocs.VotingSystem;
import uk.ac.glasgow.demosocs.impl.ui.DateUtil;
import uk.ac.glasgow.demosocs.users.User;
import uk.ac.glasgow.demosocs.users.UserStore;
import uk.ac.glasgow.demosocs.users.UserStoreImpl;
import uk.ac.glasgow.demosocs.users.Voter;

/**
 * The Class VotingSystemStub.
 * 
 * VotingSystemStub is the class used to combine the ElectionManagement component and
 * UserStore component and provide them in a useful way to the User Interface.
 * 
 * @author Project Team L
 */
public class VotingSystemStub implements VotingSystem {

	/** The user store. */
	public UserStore userStore;

	/** The current user. */
	private User currentUser;

	/** The current election. */
	private Election currentElection;

	/** The elections list. */
	private Set<Election> electionsList;

	/**
	 * Instantiates a new voting system stub.
	 */
	public VotingSystemStub(){
		userStore = new UserStoreImpl("db");
		//electionsList = new HashSet<Election>();
		this.loadElectionsFromFile("electiondb");
		currentUser = null;
		currentElection = null;
	}

	public void writeElectionsToFile(String fileName) {
		System.out.println("Trying to write to election database with filename:" + fileName);
		int numberOfElections = this.electionsList.size();
		System.out.println("There are " + numberOfElections + " to write to file.");

		try{
			// Create file 
			FileWriter storeFile = new FileWriter(fileName);
			//System.out.println("here");
			BufferedWriter out = new BufferedWriter(storeFile);
			//System.out.println("here2");
			//write the number of elections - needed to load them all back in
			out.write(numberOfElections + "\n");
			//System.out.println("here3");
			//for each election
			if (this.electionsList == null){
				System.out.println("ERROR electionsList is null");
				return;
			}
			for (Election election : this.electionsList){
				//write it to file
				out.write(election.toString());
				out.write("\n");
			}
			//Close the output stream
			out.close();
		}
		catch (Exception e){//Catch exception if any
			System.err.println("Error 2: " + e.getMessage());
		}



	}

	public void loadElectionsFromFile(String fileName) {
		System.out.println("Trying to load election db from:" + fileName);
		File storeFile = new File(fileName);
		int numberOfElections = 0;
		int numberOfElectionsLoaded = 0;
		if (storeFile.exists()){
			this.electionsList = new HashSet<Election>();
			//System.out.println("File exists.");
			Scanner in = null;
			try {
				in = new Scanner(storeFile);
				numberOfElections = Integer.parseInt(in.nextLine());
				System.out.println("There are " + numberOfElections + " election(s) in this file");
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			this.electionsList = new HashSet<Election>();
			//while (in.hasNextLine()){
			//numberOfElections = in.nextInt();
			//System.out.println("There are " + numberOfElections + " elections in this file");
			//create each election
			for (int i = 0; i < numberOfElections; i++){
				//String line = in.nextLine();
				//System.out.println("Loaded line:" + line + ".");
				/*if (line.equals("") || line.equals(" ") || line.length() < 5){
					System.out.println("We have reached the end of the file.");
					System.out.println("Loaded " + numberOfElectionsLoaded + " election(s) into the Voting System");
					System.out.println("================================================================================");
					//System.out.println(this.electionsList);
					System.out.println("================================================================================");
					return;
				}*/
				String EID = in.next();
				String electionName = in.next();
				String type = in.next();
				String manager = in.next();
				//get the dates
				Date openNomTime = null;
				Date start = null;
				Date end = null;
				try {
					openNomTime = DateUtil.parseArgumentDate(in.next(), in.next());
					start = DateUtil.parseArgumentDate(in.next(), in.next());
					end = DateUtil.parseArgumentDate(in.next(), in.next());
				} catch (SystemCommandException e) {
					System.out.println("Error processing dates");
					e.printStackTrace();
				}
				//get election state
				String stateString = in.next();
				ElectionState state = null;
				if(stateString.equals("NOT_STARTED")){
					state = ElectionState.NOT_STARTED;
				}
				else if(stateString.equals("NOMINATIONS_OPEN")){
					state = ElectionState.NOMINATIONS_OPEN;
				}
				else if(stateString.equals("STARTED")){
					state = ElectionState.STARTED;
				}
				else if(stateString.equals("ENDED")){
					state = ElectionState.ENDED;
				}
				else if(stateString.equals("PUBLISHED")){
					state = ElectionState.PUBLISHED;
				}
				//get the electorate
				int electorateSize = in.nextInt();
				Set<Voter> electorate = new HashSet<Voter>();
				for (int member = 0; member < electorateSize; member++){
					electorate.add(new Voter(in.next(), in.next(), in.next()));
				}
				//get the candidates
				int candidateSize = in.nextInt();
				Set<Candidate> candidates = new HashSet<Candidate>();
				for (int member = 0; member < candidateSize; member++){
					candidates.add(new CandidateStub(in.next(), in.next(), in.next()));
				}
				//get the votes
				int voteSize = in.nextInt();
				List<String> votes = new ArrayList<String>();
				for (int vote = 0; vote < voteSize; vote++){
					votes.add(in.next());
				}
				//get the results
				int resultsSize = in.nextInt();
				List<Candidate> results = new ArrayList<Candidate>();
				for (int result = 0; result < resultsSize; result++){
					results.add(new CandidateStub(in.next(), in.next(), in.next()));
				}
				ElectionResult electionResults = new ElectionResultStub(results);
				//create the Election
				Election election = null;
				if (type.equalsIgnoreCase("FPTP")){
					election = new ElectionStub(EID, electionName, type, manager, openNomTime, start, end, state,
							electorate, candidates, votes, electionResults);
				}
				else if (type.equalsIgnoreCase("ALTV")){
					int choices = in.nextInt();
					election = new AlternativeVote(EID, electionName, type, manager, openNomTime, start, end, state,
							electorate, candidates, votes, electionResults, choices);
				}


				this.electionsList.add(election);
				numberOfElectionsLoaded++;
				//System.out.println("Finished processing line.");
				in.nextLine();

			}


			System.out.println("Loaded " + numberOfElectionsLoaded + " election(s) into the Voting System");
			System.out.println("================================================================================");
			//System.out.println(this.electionsList);
			//System.out.println("================================================================================");
		}
		//}
		else {
			System.out.println("File does not exist");
			//this.users = new HashMap<String,User>();
		}

	}

	public User getCurrentUser() {
		return currentUser;
	}

	public void setCurrentUser(User currentUser) {
		this.currentUser = currentUser;
	}

	/**
	 * acceptNomination is used for the currentUser to accept a nomination they have
	 * received to be a candidate for an election.
	 * 
	 * This method must be called before the user becomes a candidate.
	 */
	@Override 	
	public void acceptNomination() {		
		Iterator<Candidate> candidates = currentElection.getCandidates().iterator();
		while(candidates.hasNext()){
			CandidateStub candidate = (CandidateStub) candidates.next();
			if(candidate.getGUID().equalsIgnoreCase(currentUser.getGUID())){
				candidate.setAcceptedNomination();
				System.out.println("Nomination accepted in election "+ currentElection.getEID().toString());
				break;
			}	
		}
	}

	/**
	 * castVote casts the given vote (for the current user)on the currently selected 
	 * election.
	 * 
	 * @param vote the Vote that has to be cast.
	 */
	@Override
	public void castVote(Vote vote) {
		
		if (this.currentElection.getClass() == AlternativeVote.class){
			System.out.println("WE ARE DEALING WITH A ALT VOTE");
			this.castAltVote(vote);
		}
		
		ElectionStub election = (ElectionStub)currentElection;		
		Iterator<Voter> electorateMembers = election.getElectorate().iterator();
		while(electorateMembers.hasNext()){
			Voter member = electorateMembers.next();
			if(member.getGUID().equalsIgnoreCase(currentUser.getGUID())){
				Iterator<Candidate> candidates = currentElection.getCandidates().iterator();
				while(candidates.hasNext()){					
					CandidateStub candidate = (CandidateStub) candidates.next();
					if(candidate.getGUID().equalsIgnoreCase(((OneOfNVoteStub) vote).getCandidateGUID())){
						if(candidate.hasAcceptedNomanination()){
							System.out.println(election.getVotes().toString());
							election.getVotes().add(candidate.getGUID());
							break;
						}
						else{
							System.out.println("The candidate has not acceptated the nomination yet.");
							break;
						}
					}
					else{
						System.out.println("The user " + candidate.getGUID().toString() + 
								" is not a candidate in the election " + election.getEID().toString());
						break;
					}
				}
			}
		}
	}
	
	public void castAltVote(Vote vote){
		AltVote altVote = (AltVote) vote;
		AlternativeVote election = (AlternativeVote) this.currentElection;
		
		
	}

	/**
	 * createElection is used to created an election with the given name and typeID (both
	 * Strings).
	 * 
	 * @param name the name of the election to be created
	 * @param typeID the type of the election that is to be created (e.g FPTP)
	 */
	@Override
	public void createElection(String name, String typeID) {


		if (currentUser.isElectionManager() == false){
			System.err.println("CREATE: Current User is not an Election Manager");
			return;
		}
		//NEED TO SURROUND THIS WITH IF STATEMENT IF WE ARE TOO SUPPORT NEW ELECTION TYPES
		if (typeID.equalsIgnoreCase("FPTP")){
			ElectionStub newElection = new ElectionStub(name, typeID);
			newElection.setEID(""+this.electionsList.size());
			this.electionsList.add(newElection);
			this.currentElection = (Election)newElection;
		}
		else if (typeID.equalsIgnoreCase("REF")){
			Referendum newElection = new Referendum(name, typeID);
			newElection.setEID(""+this.electionsList.size());
			this.electionsList.add(newElection);
			this.currentElection = (Election)newElection;
		}
		else if (typeID.equalsIgnoreCase("ALTV")){
			AlternativeVote newElection = new AlternativeVote(name, typeID);
			newElection.setEID(""+this.electionsList.size());
			this.electionsList.add(newElection);
			this.currentElection = (Election)newElection;
		}
		//this.writeElectionsToFile("electiondb");
	}


	/**
	 * editElection is used to make changes to the given fields of the currently selected
	 * election.
	 * 
	 * @param openNominations the Date that nominations open
	 * @param start the start Date for voting
	 * @param end the end Date for voting
	 * @param electoratelectionID the electorate ID for voters in this election
	 */
	@Override
	public void editElection(Date openNominations, Date start, Date end,
			String electoratelectionID) {
		if (currentElection == null){
			//System.err.println("EDIT:No Current Election set.");
			return;
		}
		if (currentUser.isElectionManager() == false){
			//System.err.println("EDIT:Current User is not an Election Manager");
			return;
		}
		
		if (currentElection.getClass() == ElectionStub.class){
			ElectionStub currentElection = (ElectionStub) this.currentElection;
			currentElection.setEID(electoratelectionID);
			currentElection.setOpenNominations(openNominations);
			currentElection.setEnd(end);
			currentElection.setStart(start);
			
			Set<Voter> electorate = new HashSet<Voter>();
			
			for(String str: userStore.getUsers().keySet()){
				if(str.substring(0, 2).equalsIgnoreCase(electoratelectionID)){
					Voter v = new Voter(userStore.getUser(str, "password"));
					electorate.add(v);
				}
			}
			
			currentElection.setElectorate(electorate);
			Iterator<Voter> iter =  electorate.iterator();
			while(iter.hasNext()){
				System.out.println(iter.next());
			}
			electionsList.add(currentElection);
			this.writeElectionsToFile("electiondb");
		}
		else if (currentElection.getClass() == AlternativeVote.class){
			AlternativeVote currentElection = (AlternativeVote) this.currentElection;
			currentElection.setEID(electoratelectionID);
			currentElection.setOpenNominations(openNominations);
			currentElection.setEnd(end);
			currentElection.setStart(start);
			
			Set<Voter> electorate = new HashSet<Voter>();
			
			for(String str: userStore.getUsers().keySet()){
				if(str.substring(0, 2).equalsIgnoreCase(electoratelectionID)){
					Voter v = new Voter(userStore.getUser(str, "password"));
					electorate.add(v);
				}
			}
			
			currentElection.setElectorate(electorate);
			Iterator<Voter> iter =  electorate.iterator();
			while(iter.hasNext()){
				System.out.println(iter.next());
			}
			//electionsList.add(currentElection);
			this.writeElectionsToFile("electiondb");
		}
		else {
			//referendum
			ElectionStub currentElection = (ElectionStub) this.currentElection;
			currentElection.setEID(electoratelectionID);
			currentElection.setOpenNominations(openNominations);
			currentElection.setEnd(end);
			
			Set<Voter> electorate = new HashSet<Voter>();
			
			for(String str: userStore.getUsers().keySet()){
				if(str.substring(0, 2).equalsIgnoreCase(electoratelectionID)){
					Voter v = new Voter(userStore.getUser(str, "password"));
					electorate.add(v);
				}
			}
			
			currentElection.setElectorate(electorate);
			/*Iterator<Voter> iter =  electorate.iterator();
			while(iter.hasNext()){
				System.out.println(iter.next());
			}*/
			//electionsList.add(currentElection);
			this.writeElectionsToFile("electiondb");
		}


		// TODO these voters have been manually added to the electorate
		// we need to have separate files  and when we run the program
		// to read the content from each file into separate electorates
		// e.g electorate1.txt -> read line by line -> add into Set<Voter> electorate
		// for the electionID e.g 1234
		/**Set<Voter> electorate = new HashSet<Voter>();
		
		for(String str: userStore.getUsers().keySet()){
			if(str.substring(0, 2).equalsIgnoreCase(electoratelectionID)){
				Voter v = new Voter(userStore.getUser(str, "password"));
				electorate.add(v);
			}
		}
/*		
		Voter v1 = new Voter("Archie","White","1000000w");
		Voter v2 = new Voter("Alfie","Mitchell", "1000001m");
		Voter v3 = new Voter("Max", "Adams", "1000002a");
		electorate.add(v1);
		electorate.add(v2);
		electorate.add(v3);

*/		/**currentElection.setElectorate(electorate);
		Iterator<Voter> iter =  electorate.iterator();
		while(iter.hasNext()){
			System.out.println(iter.next());
		}
		electionsList.add(currentElection);
		this.writeElectionsToFile("electiondb");*/
	}

	/**
	 * Gets the current election list.
	 * 
	 * @return A Set of Elections representing all of the elections currently on the system.
	 */
	@Override
	public Set<Election> getElections() {
		//System.out.println(electionsList.toString());
		return electionsList;
	}

	/**
	 * Gets the list of elections that the current user has been nominated as a Candidate
	 * in.
	 * 
	 * @return A Set of Elections that the current user is nominated in.
	 */
	@Override
	public Set<Election> getNominations() {
		Set<Election> nominationsList = new HashSet<Election>();
		Iterator<Election> electionsIterator = electionsList.iterator();
		while(electionsIterator.hasNext()){
			Election election = electionsIterator.next();
			Iterator<Candidate> candidateIterator = election.getCandidates().iterator();
			while(candidateIterator.hasNext()){

				// Check if the user is a candidate in the election
				if( candidateIterator.next().getGUID().equalsIgnoreCase(currentUser.getGUID())){
					nominationsList.add(election);
					break;
				}
			}
		}
		return nominationsList;
	}


	/**
	 * Nominates a candidate to stand in the currently selected election, 
	 * if: there is a currently selected election for which nominations are open; 
	 * the GUID is for a valid user who is a member of the electorate for the election; 
	 * and the currently selected user is a member of the electorate for the currently 
	 * specified election.
	 * 
	 * @param GUID String representing the GUID of the Candidate that is to be nominated.
	 */	
	@Override
	public void nominateCandidate(String GUID) {
		
		if (this.currentElection.getClass() == ElectionStub.class){
			System.out.println("WE ARE DEALING WITH AN ELECTIONSTUB");
			//currentElection = (ElectionStub)election;
		}
		else if (this.currentElection.getClass() == Referendum.class){
			System.out.println("WE ARE DEALING WITH A REFERENDUM");
			//currentElection = (Referendum)election;
		}

		System.out.println("In votingSystemstub nominate Candidate "+ currentElection.getOpenNominations().toString());
		System.out.println("In votingSystemstub nominate Candidate "+ currentElection.getStart().toString());
		System.out.println("In votingSystemstub nominate Candidate "+ currentElection.getEnd().toString());

		if(currentElection.getOpenNominations().before(Calendar.getInstance().getTime())){
			System.out.println("In votingSystemStub nominateCandidate method");

			ElectionStub cElection = (ElectionStub) currentElection;
			Iterator<Voter> electionMember = cElection.getElectorate().iterator();

			while(electionMember.hasNext()){
				Voter voter = electionMember.next();
				if(voter.getGUID().equalsIgnoreCase(GUID)){
					//Candidate candidate = (Candidate) electionMember.next();
					System.out.println("In nominate candidate");
					System.out.println("In votingSystemStub adding candidate "+GUID + " to the election "+cElection.getEID().toString());
					cElection.addCandidate(voter);
					break;
				}
			}		

		}
		else{
			System.out.println("Nominations not started yet.");
		}
	}

	/**
	 * This method prints the results of the current election to the User Interface
	 * (standard output).
	 */
	@Override
	public void publishResults() {
		ElectionStub election = (ElectionStub) currentElection;
		if(election.getElectionManager().equalsIgnoreCase(currentUser.getGUID()))
			if(currentElection.getEnd().before(Calendar.getInstance().getTime())){
				ElectionResult result = countTheVotes(currentElection);
				System.out.println(result.toString());
				/*
				for(Candidate candidate: result.getWinningCandidates())
					System.out.println(candidate.getCandidateForename() +" "+ candidate.getCandidateSurname() + " "+
							candidate.getGUID());
				 */
			}	
	}

	/**
	 * Sets the current election to the election with the given ID.
	 * 
	 * @param String representing electionID of the election that is to be set to current election
	 */
	@Override
	public void setCurrentElection(String electionID) {
		Iterator<Election> electionsIterator = electionsList.iterator();
		while(electionsIterator.hasNext()){
			Election election = electionsIterator.next();
			if(election.getEID().equalsIgnoreCase(electionID)){
				if (election.getClass() == ElectionStub.class){
					System.out.println("WE ARE DEALING WITH AN ELECTIONSTUB");
					currentElection = (ElectionStub)election;
				}
				else if (election.getClass() == Referendum.class){
					System.out.println("WE ARE DEALING WITH A REFERENDUM");
					currentElection = (Referendum)election;
				}
				break;
			}
		}
	}


	/**
	 * Set the current user to the GUID given if password passes authenticication.
	 * 
	 * @param GUID String GUID of User
	 * @param String password for this User
	 * @return true if successful (correct password), false if unsuccessful (incorrect password)
	 */
	public boolean setCurrentUser(String GUID, String password) {

		currentUser = userStore.getUser(GUID,password);
		if (currentUser == null)
			return false; //authentication failed and returned null
		else
			return true; //authentication was successful
	}

	/**
	 * Return the current election.
	 * 
	 * @return Current Election of the Voting System.
	 */
	@Override
	public Election getCurrentElection() {
		return this.currentElection;
	}

	/**
	 * Returns the GUID of the current User.
	 * 
	 * @return String representing GUID of current user.
	 */
	public String getCurrentUserGUID() {
		return currentUser.getGUID();
	}

	/**
	 * Sets the current election.
	 *
	 * @param currentElection the new current election
	 */
	public void setCurrentElection(Election currentElection) {
		this.currentElection = new ElectionStub(currentElection);
	}

	/**
	 * Count the votes.
	 *
	 * @param currentElection the current election
	 * @return the election result
	 */
	public ElectionResult countTheVotes(Election currentElection){
		
		
		if (this.currentElection.getClass() == ElectionStub.class){
			//System.out.println("WE ARE DEALING WITH AN ELECTIONSTUB");
			ElectionStub election =(ElectionStub) currentElection;
			HashMap< Candidate, Integer> results = new HashMap<Candidate, Integer>();
			Iterator<Candidate> candidates = election.getCandidates().iterator();
			while(candidates.hasNext())
				results.put(candidates.next(), 0);

			for(String guid: election.getVotes()){
				for(Candidate c:results.keySet()){
					if(c.getGUID().equalsIgnoreCase(guid)){
						int value = results.get(c);
						value++;
						results.put(c, value);
					}
				}
			}
			
			List<Candidate> candidatesList = new ArrayList<Candidate>();
			for(Candidate cand: results.keySet()){
				candidatesList.add(cand);
			}

			return new ElectionResultStub(candidatesList);
		}
		else if (this.currentElection.getClass() == Referendum.class){
			System.out.println("WE ARE DEALING WITH A REFERENDUM");
			return new ElectionResultStub();
		}
		else {//(this.currentElection.getClass() == AlternativeVote.class){
			System.out.println("WE ARE DEALING WITH An ALTERNATIVE VOTE");
			AlternativeVote election =(AlternativeVote) currentElection;
			return new ElectionResultStub();
		}
		//return null;
		

		/*		Iterator<Voter> voters = election.getElectorate().iterator();

		while(voters.hasNext()){
			Candidate votedFor = voters.next().getVotedFor();
			int count = results.get(votedFor);
			results.remove(votedFor);
			count++;
			results.put(votedFor, count);
			break;
		}*/		
		
	}
}
